"""寄售单功能"""

from datetime import datetime, timedelta, date
from collections import defaultdict

from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare
from odoo.addons import decimal_precision as dp
from odoo.osv import expression


class Sale(models.Model):
    _inherit = 'sale.order'

    is_consignment = fields.Boolean('寄售', copy=False)
    is_consignment_finish = fields.Boolean('寄售完成', copy=False)
    consignment_date = fields.Date('寄售完成时间', copy=False)
    nd_confirm_out = fields.Boolean('寄售出库完成', copy=False)
    invoice_status = fields.Selection([
        ('invoiced', 'Fully Invoiced'),
        ('to invoice', 'To Invoice'),
        ('no', 'Nothing to Invoice'),
        ('not_all','部分开票')
    ], string='Invoice Status', compute='_get_invoiced', store=True, readonly=True)
    delivery_status = fields.Selection([
        ('all', '全部交货'),
        ('not all', '部分交货'),
        ('no', '未交货')],
        string='交货状态', compute='_get_delivery', store=True, readonly=True)

    @api.depends('order_line.qty_delivered',
                 'order_line.product_uom_qty')
    def _get_delivery(self):
        for order in self:
            product_uom_qyt = sum(order.order_line.mapped('product_uom_qty'))
            qty_delivered = sum(order.order_line.mapped('qty_delivered'))
            if qty_delivered == 0:
                order.delivery_status = 'no'
            elif product_uom_qyt == qty_delivered:
                order.delivery_status = 'all'
            else:
                order.delivery_status = 'not all'

    @api.depends('state', 'order_line.invoice_status', 'order_line.invoice_lines')
    def _get_invoiced(self):
        """
        Compute the invoice status of a SO. Possible statuses:
        - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
          invoice. This is also the default value if the conditions of no other status is met.
        - to invoice: if any SO line is 'to invoice', the whole SO is 'to invoice'
        - invoiced: if all SO lines are invoiced, the SO is invoiced.
        - upselling: if all SO lines are invoiced or upselling, the status is upselling.

        The invoice_ids are obtained thanks to the invoice lines of the SO lines, and we also search
        for possible refunds created directly from existing invoices. This is necessary since such a
        refund is not directly linked to the SO.
        """
        # Ignore the status of the deposit product
        deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id()
        line_invoice_status_all = [(d['order_id'][0], d['invoice_status']) for d in
                                   self.env['sale.order.line'].read_group(
                                       [('order_id', 'in', self.ids), ('product_id', '!=', deposit_product_id.id)],
                                       ['order_id', 'invoice_status'], ['order_id', 'invoice_status'], lazy=False)]
        for order in self:
            invoice_ids = order.order_line.mapped('invoice_lines').mapped('invoice_id').filtered(
                lambda r: r.type in ['out_invoice', 'out_refund'])
            # Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
            # 'account.invoice.refund')
            # use like as origin may contains multiple references (e.g. 'SO01, SO02')
            refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id),
                                          ('type', 'in', ('out_invoice', 'out_refund'))])
            invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')])

            # Search for refunds as well
            domain_inv = expression.OR([
                ['&', ('origin', '=', inv.number), ('journal_id', '=', inv.journal_id.id)]
                for inv in invoice_ids if inv.number
            ])
            if domain_inv:
                refund_ids = self.env['account.invoice'].search(expression.AND([
                    ['&', ('type', '=', 'out_refund'), ('origin', '!=', False)],
                    domain_inv
                ]))
            else:
                refund_ids = self.env['account.invoice'].browse()

            line_invoice_status = [d[1] for d in line_invoice_status_all if d[0] == order.id]

            if order.state not in ('sale', 'done'):
                invoice_status = 'no'
            elif any(invoice_status == 'to invoice' for invoice_status in line_invoice_status):
                invoice_status = 'not_all'
            elif line_invoice_status and all(invoice_status == 'invoiced' for invoice_status in line_invoice_status):
                invoice_status = 'invoiced'
            elif line_invoice_status and all(invoice_status == 'to invoiced' for invoice_status in line_invoice_status):
                invoice_status = 'to invoice'
            else:
                invoice_status = 'no'

            order.update({
                'invoice_count': len(set(invoice_ids.ids + refund_ids.ids)),
                'invoice_ids': invoice_ids.ids + refund_ids.ids,
                'invoice_status': invoice_status
            })

    @api.multi
    def nd_consignment_price_wizard(self):
        """寄售价格向导"""
        if not self.nd_confirm_out:
            view = self.env.ref('nd_consignment.NDSaleOrderPriceEditView')
            return {
                'name': '添加真实销售价格',
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'sale.order',
                'views': [(view.id, 'form')],
                # 'view_id': view.id,
                'target': 'new',
                'res_id': self.id,
            }

    @api.multi
    def nd_confirm_out_picking(self):
        """确认寄售实际出库, TODO 金额、退货等处理逻辑
        可以部分退货：在内部调拨单上执行退货，修改出库单为实际数量后再确认
        可以修改单价：需要修改生成的发票明细
        """
        self.ensure_one()
        if self.is_consignment_finish:
            self.nd_confirm_out = True

            # 先更新发票明细上的金额
            for line in self.order_line:
                pre_account_lines = line.pre_account_line_ids
                if sum(pre_account_lines.mapped('invoiced_amount')) > 0 and pre_account_lines.business_untaxed_amount != line.price_subtotal:
                    raise UserError(f'此销售单已开发票，不能修改金额！')
                pre_account_lines.write({
                        'quantity': line.product_uom_qty - line.nd_return_qty,
                        'business_untaxed_amount': line.price_subtotal,
                        'avg_price': line.price_unit,
                        'business_tax': line.price_tax,
                        'business_amount': line.price_total})
            # 在寄售的内部调拨单上执行退货数量
            return_info = defaultdict(lambda: 0)    # {product_id: return_qty}  单位？
            for line in self.order_line:
                if line.nd_return_qty > 0:
                    return_info[line.product_id] += line.nd_return_qty

            consignment_pickings = self.picking_ids.filtered(lambda x: x.picking_type_id.code == 'internal' and x.state in ['done'])
            return_wizards = self.env['stock.return.picking']
            for picking in consignment_pickings:
                if sum(return_info.values()) > 0:
                    self = self.with_context(active_ids=picking.ids, active_id=picking.id)
                    return_wizard = self.env['stock.return.picking'].create({})
                    for w_line in return_wizard.product_return_moves:
                        if w_line.product_id in return_info:
                            # quantity = w_line.move_id.product_qty - sum(w_line.move_id.move_dest_ids.filtered(lambda m: m.state in ['partially_available', 'assigned', 'done']).\
                            #                       mapped('move_line_ids').mapped('product_qty'))
                            return_qty = min(w_line.move_id.quantity_done, return_info[w_line.product_id])
                            w_line.quantity = return_qty
                            return_info[w_line.product_id] -= return_qty
                        else:
                            w_line.quantity = 0

                    return_wizards |= return_wizard

            # 修改出库单为实际数量
            out_picking = self.picking_ids.filtered(lambda x: x.picking_type_id.code == 'outgoing' and x.state not in ['done', 'cancel'])
            # TODO 这里随机选择批次来削减退货的数量
            for line in self.order_line:
                return_qty = line.nd_return_qty
                for move in line.move_ids:
                    for line in move.move_line_ids:
                        if return_qty > 0:
                            minus_qty = min(return_qty, line.qty_done)
                            line.qty_done -= minus_qty
                            return_qty -= minus_qty

            if out_picking:
                out_picking.action_done()
                backorder_pick = self.env['stock.picking'].search([('backorder_id', '=', out_picking.id)])
                backorder_pick.with_context(no_sync=True).action_cancel()

            # TODO 部分情况下可能无需同步到WMS，但先全部同步.
            # 先确认出库单再退货寄售单，因为寄售单会取消出库单的明细stock.move.line导致出库确认失败
            if return_wizards:
                return_wizards.create_returns()

    @api.multi
    def action_confirm(self):
        """添加寄售单同步逻辑
        对于寄售单，只同步其产生的内部调拨单，产生的出库单则不同步
        """
        if self.is_consignment:
            self = self.with_context(no_sync=True)

        res = super().action_confirm()

        if self.is_consignment:
            sync_picking = self.mapped('picking_ids').filtered(lambda x: x.picking_type_id.code == 'internal')
            self.push_consignment_stocks_to_wms(sync_picking)
        return res

    @api.model
    def create(self, val):
        res = super().create(val)
        if val.get('is_consignment') is True:
            res.order_line.update_nd_route_id()
        return res

    @api.multi
    def write(self, val):
        res = super().write(val)
        if self.is_consignment:
            self.order_line.update_nd_route_id()
        return res

    def _update_lot_into_move(self, sale_lines):
        """在原来的基础上，还要额外向多部路线产生的额外的内部调拨明细里
        填充库位、批次信息。"""
        res = super()._update_lot_into_move(sale_lines)
        if self.is_consignment:
            for sale_line in sale_lines:
                # 正常的调拨明细
                move_id = sale_line.move_ids
                # 寄售路线产生的额外的内部调拨明细,因为内部调拨在正常调拨之前，所以是move_orig_ids
                orig_move_id = move_id.move_orig_ids
                orig_move_line_ids = orig_move_id.mapped('move_line_ids')

                for i in range(len(sale_line.nd_lot_lines) - len(orig_move_line_ids)):
                    orig_move_line_ids |= self.env['stock.move.line'].create(orig_move_id._prepare_move_line_vals())   # TODO 后续如果有预留，这里徐太需要改动

                for nd_lot_line, move_line in zip(sale_line.nd_lot_lines, orig_move_line_ids):
                    move_line.write({'lot_id': nd_lot_line.lot_id.id,
                                     'qty_done': nd_lot_line.qty,
                                     'location_id': nd_lot_line.location_src_id.id})
        return res


class Location(models.Model):
    _inherit = 'stock.location'

    is_consignment = fields.Boolean('寄售')


class GSPCheck(models.AbstractModel):
    _inherit = 'gsp.business.check'

    def _get_legal_locations_domain(self):
        domains = super()._get_legal_locations_domain()
        domains.append(('is_consignment', '!=', True))
        return domains


class SaleLine(models.Model):
    _inherit = 'sale.order.line'

    # is_consignment = fields.Boolean('寄售', related="order_id.is_consignment")
    nd_return_qty = fields.Float('寄售退货数量', copy=False)
    route_id = fields.Many2one(copy=False)

    def update_nd_route_id(self):
        """如果选择了寄售，则自动填充路线"""
        consignment_route_id = self.env.ref('nd_consignment.nd_consignment_route')
        for line in self:
            if line.order_id.is_consignment:
                line.route_id = consignment_route_id
            else:
                line.route_id = False

